GDK W32: Test for IME correctly
authorРуслан Ижбулатов <lrn1986@gmail.com>
Fri, 22 Mar 2019 15:10:02 +0000 (15:10 +0000)
committerРуслан Ижбулатов <lrn1986@gmail.com>
Fri, 22 Mar 2019 16:58:59 +0000 (16:58 +0000)
ImmIsIME() doesn't work (always returns TRUE) since Vista.
Use ITfActiveLanguageProfileNotifySink to detect TSF changes,
which are equal to IME changes for us.

Also make sure that IMMultiContext re-loads the IM when keyboard layout
changes, otherwise there's a subtle bug that could happen:
* Run GTK application with non-IME layout (US, for example)
* Focus on an editable widget (GtkEntry, for example)
* IM Context is initialized to use the simple IM
* Switch to an IME layout (such as Korean)
* Start typing
* Since IME module is not loaded yet, keypresses are handled
  by a default MS IME handler
* Once IME commits a character, GDK will get a WM_KEYDOWN,
  which will trigger a GdkKeyEvent, which will be handled by
  an event filter in IM Context, which will finally re-evaluate
  its status and load IME, and only after that GTK will get
  to handle IME by itself - but by that point input would
  already be broken.
To avoid this we can emit a dummy event (with Void keyval),
which will cause IM Context to load the appropriate module
immediately.

gdk/win32/gdkdisplay-win32.c
gdk/win32/gdkevents-win32.c
gdk/win32/gdkglobals-win32.c
gdk/win32/gdkmain-win32.c
gdk/win32/gdkwin32langnotification.c [new file with mode: 0644]
gdk/win32/gdkwin32langnotification.h [new file with mode: 0644]
gdk/win32/meson.build

index 4a7182758f4c5330590efafac21428353dfb065c..bbfda32729f88b0b9dda0c2d0282719edc1d3208 100644 (file)
@@ -38,6 +38,8 @@
 
 #include <dwmapi.h>
 
+#include "gdkwin32langnotification.h"
+
 static int debug_indent = 0;
 
 /**
@@ -536,6 +538,7 @@ _gdk_win32_display_open (const gchar *display_name)
                                       NULL);
   _gdk_device_manager->display = _gdk_display;
 
+  _gdk_win32_lang_notification_init ();
   _gdk_drag_init ();
   _gdk_drop_init ();
 
@@ -701,6 +704,7 @@ gdk_win32_display_finalize (GObject *object)
 
   _gdk_win32_display_finalize_cursors (display_win32);
   _gdk_win32_dnd_exit ();
+  _gdk_win32_lang_notification_exit ();
 
   g_ptr_array_free (display_win32->monitors, TRUE);
 
index 712115072af8191ca0999aba3374e88192fec3b4..2ad57e056869016f65e1f977915a1a1bfb281c81 100644 (file)
@@ -2113,7 +2113,6 @@ gdk_event_translate (MSG  *msg,
     case WM_INPUTLANGCHANGE:
       _gdk_input_locale = (HKL) msg->lParam;
       _gdk_win32_keymap_set_active_layout (GDK_WIN32_KEYMAP (_gdk_win32_display_get_keymap (_gdk_display)), _gdk_input_locale);
-      _gdk_input_locale_is_ime = ImmIsIME (_gdk_input_locale);
       GetLocaleInfo (MAKELCID (LOWORD (_gdk_input_locale), SORT_DEFAULT),
                     LOCALE_IDEFAULTANSICODEPAGE,
                     buf, sizeof (buf));
@@ -2125,6 +2124,20 @@ gdk_event_translate (MSG  *msg,
                         (gpointer) msg->lParam, _gdk_input_locale_is_ime ? " (IME)" : "",
                         _gdk_input_codepage));
       gdk_display_setting_changed (display, "gtk-im-module");
+
+      /* Generate a dummy key event to "nudge" IMContext */
+      event = gdk_event_new (GDK_KEY_PRESS);
+      event->any.surface = window;
+      event->key.time = _gdk_win32_get_next_tick (msg->time);
+      event->key.keyval = GDK_KEY_VoidSymbol;
+      event->key.hardware_keycode = 0;
+      event->key.group = 0;
+      gdk_event_set_scancode (event, 0);
+      gdk_event_set_device (event, device_manager_win32->core_keyboard);
+      gdk_event_set_source_device (event, device_manager_win32->system_keyboard);
+      event->key.is_modifier = FALSE;
+      event->key.state = 0;
+      _gdk_win32_append_event (event);
       break;
 
     case WM_SYSKEYUP:
index 523e7c3a3b2936aac75bef4d804d34d62e2007f0..12e228be2c7b05f2d75a6251a9cf50c42fc40aa9 100644 (file)
@@ -39,7 +39,7 @@ HINSTANCE       _gdk_app_hmodule;
 gint             _gdk_input_ignore_core;
 
 HKL              _gdk_input_locale;
-gboolean         _gdk_input_locale_is_ime;
+gboolean         _gdk_input_locale_is_ime = FALSE;
 UINT             _gdk_input_codepage;
 
 gint             _gdk_input_ignore_wintab = FALSE;
index dc878fa522d91b5a7603029974157ac7d79a6e19..2506d53f115743cf8d40ee2ba95e71333f467fcf 100644 (file)
@@ -83,7 +83,6 @@ _gdk_win32_surfaceing_init (void)
   _gdk_display_hdc = CreateDC ("DISPLAY", NULL, NULL, NULL);
   _gdk_input_locale = GetKeyboardLayout (0);
   _gdk_win32_keymap_set_active_layout (GDK_WIN32_KEYMAP (_gdk_win32_display_get_keymap (_gdk_display)), _gdk_input_locale);
-  _gdk_input_locale_is_ime = ImmIsIME (_gdk_input_locale);
   GetLocaleInfo (MAKELCID (LOWORD (_gdk_input_locale), SORT_DEFAULT),
                 LOCALE_IDEFAULTANSICODEPAGE,
                 buf, sizeof (buf));
diff --git a/gdk/win32/gdkwin32langnotification.c b/gdk/win32/gdkwin32langnotification.c
new file mode 100644 (file)
index 0000000..94814ec
--- /dev/null
@@ -0,0 +1,172 @@
+/* GDK - The GIMP Drawing Kit
+ * Copyright (C) 2019 Руслан Ижбулатов <lrn1986@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#define COBJMACROS
+#include <msctf.h>
+
+#include <gdk/gdk.h>
+
+#include "gdkprivate-win32.h"
+
+struct _GdkWin32ALPNSink
+{
+  ITfActiveLanguageProfileNotifySink itf_alpn_sink;
+
+  gint ref_count;
+};
+
+typedef struct _GdkWin32ALPNSink GdkWin32ALPNSink;
+
+static GdkWin32ALPNSink *actlangchangenotify = NULL;
+static ITfSource *itf_source = NULL;
+static DWORD actlangchangenotify_id = 0;
+
+static ULONG STDMETHODCALLTYPE
+alpn_sink_addref (ITfActiveLanguageProfileNotifySink *This)
+{
+  GdkWin32ALPNSink *alpn_sink = (GdkWin32ALPNSink *) This;
+  int ref_count = ++alpn_sink->ref_count;
+
+  return ref_count;
+}
+
+static HRESULT STDMETHODCALLTYPE
+alpn_sink_queryinterface (ITfActiveLanguageProfileNotifySink *This,
+                          REFIID                              riid,
+                          LPVOID                             *ppvObject)
+{
+  *ppvObject = NULL;
+
+  if (IsEqualGUID (riid, &IID_IUnknown))
+    {
+      ITfActiveLanguageProfileNotifySink_AddRef (This);
+      *ppvObject = This;
+      return S_OK;
+    }
+
+  if (IsEqualGUID (riid, &IID_ITfActiveLanguageProfileNotifySink))
+    {
+      ITfActiveLanguageProfileNotifySink_AddRef (This);
+      *ppvObject = This;
+      return S_OK;
+    }
+
+  return E_NOINTERFACE;
+}
+
+static ULONG STDMETHODCALLTYPE
+alpn_sink_release (ITfActiveLanguageProfileNotifySink *This)
+{
+  GdkWin32ALPNSink *alpn_sink = (GdkWin32ALPNSink *) This;
+  int ref_count = --alpn_sink->ref_count;
+
+  if (ref_count == 0)
+    {
+      g_free (This);
+    }
+
+  return ref_count;
+}
+
+static HRESULT STDMETHODCALLTYPE
+alpn_sink_on_activated (ITfActiveLanguageProfileNotifySink *This,
+                        REFCLSID                            clsid,
+                        REFGUID                             guidProfile,
+                        BOOL                                fActivated)
+{
+  _gdk_input_locale_is_ime = fActivated;
+  return S_OK;
+}
+
+static ITfActiveLanguageProfileNotifySinkVtbl alpn_sink_vtbl = {
+  alpn_sink_queryinterface,
+  alpn_sink_addref,
+  alpn_sink_release,
+  alpn_sink_on_activated,
+};
+
+static GdkWin32ALPNSink *
+alpn_sink_new ()
+{
+  GdkWin32ALPNSink *result;
+
+  result = g_new0 (GdkWin32ALPNSink, 1);
+  result->itf_alpn_sink.lpVtbl = &alpn_sink_vtbl;
+  result->ref_count = 0;
+
+  ITfActiveLanguageProfileNotifySink_AddRef (&result->itf_alpn_sink);
+
+  return result;
+}
+
+
+void
+_gdk_win32_lang_notification_init ()
+{
+  HRESULT hr;
+  ITfThreadMgr *itf_threadmgr;
+
+  CoInitializeEx (NULL, COINIT_APARTMENTTHREADED);
+
+  if (actlangchangenotify != NULL)
+    return;
+
+  hr = CoCreateInstance (&CLSID_TF_ThreadMgr,
+                         NULL,
+                         CLSCTX_INPROC_SERVER,
+                         &IID_ITfThreadMgr,
+                         (LPVOID *) &itf_threadmgr);
+
+  if (!SUCCEEDED (hr))
+    return;
+
+  hr = ITfThreadMgr_QueryInterface (itf_threadmgr, &IID_ITfSource, (VOID **) &itf_source);
+  ITfThreadMgr_Release (itf_threadmgr);
+
+  if (!SUCCEEDED (hr))
+    return;
+
+  actlangchangenotify = alpn_sink_new ();
+
+  hr = ITfSource_AdviseSink (itf_source,
+                             &IID_ITfActiveLanguageProfileNotifySink,
+                             (IUnknown *) actlangchangenotify,
+                             &actlangchangenotify_id);
+
+  if (!SUCCEEDED (hr))
+    {
+      ITfActiveLanguageProfileNotifySink_Release (&actlangchangenotify->itf_alpn_sink);
+      actlangchangenotify = NULL;
+      ITfSource_Release (itf_source);
+      itf_source = NULL;
+    }
+}
+
+void
+_gdk_win32_lang_notification_exit ()
+{
+  if (actlangchangenotify != NULL && itf_source != NULL)
+    {
+      ITfSource_UnadviseSink (itf_source, actlangchangenotify_id);
+      ITfSource_Release (itf_source);
+      ITfActiveLanguageProfileNotifySink_Release (&actlangchangenotify->itf_alpn_sink);
+    }
+
+  CoUninitialize ();
+}
diff --git a/gdk/win32/gdkwin32langnotification.h b/gdk/win32/gdkwin32langnotification.h
new file mode 100644 (file)
index 0000000..c67e18f
--- /dev/null
@@ -0,0 +1,26 @@
+/*
+ * gdkwin32langnotification.h
+ *
+ * Copyright 2019 Руслан Ижбулатов <lrn1986@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GDK_WIN32_LANGNOTIFICATION_H__
+#define __GDK_WIN32_LANGNOTIFICATION_H__
+
+void _gdk_win32_lang_notification_init (void);
+void _gdk_win32_lang_notification_exit (void);
+
+#endif
\ No newline at end of file
index f96fc6b39482eacb2e35756fd7850679b7769ccb..45915f2a8f7193330157acb2385472f3cfb482c4 100644 (file)
@@ -17,6 +17,7 @@ gdk_win32_sources = files([
   'gdkglobals-win32.c',
   'gdkhdataoutputstream-win32.c',
   'gdkkeys-win32.c',
+  'gdkwin32langnotification.c',
   'gdkmain-win32.c',
   'gdkmonitor-win32.c',
   'gdkproperty-win32.c',